﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

using System.IO;
using System.Configuration;

using chad.azure.diagnostics.export;
using chad.azure.diagnostics.data;

using chad.azure.diagnostics.wad;

using OfficeOpenXml;
using OfficeOpenXml.Style;
using OfficeOpenXml.Drawing.Chart;

using System.Text.RegularExpressions;

namespace chad.azure.diagnostics.excel
{
    public enum EventSummaryType
    {
        ByDeploymentByRoleByLevel,
        ByDeploymentByRoleByMessage,
        ByDeploymentByRoleByChannelBySourceByLevel,
        ByDeploymentByRoleByChannelBySourceByLevelByEventId
    }

    public class ExcelReportBuilder : ReportBuilder
    {
        #region Global Variables

        private const string CONFIG_PARAMETER_EVENT_ID = "EventIdFilterForLessThanOrEqualTo";
        private const string CONFIG_PARAMETER_EVENT_REGEXMATCH = "EventMessageFilterRegexMatch";
        private const string CONFIG_PARAMETER_EVENT_MAXROWCOUNT = "EventMessageMaxRowCount";

        ExcelPackage _pkg = new ExcelPackage();

        #endregion

        public ExcelReportBuilder()
        { }

        #region Full Report Generation

        public static string GenerateReportFromConfiguration(ReportConfiguration config)
        {
            DateTime datetimeStartUtc = DateTime.UtcNow.AddMinutes(config.DateTimeStartMinutesOffset);
            DateTime datetimeEndUtc = DateTime.UtcNow.AddMinutes(config.DateTimeEndMinutesOffset);

            String fileAndPath = String.Format(@"{0}\{1}_{2}_{3}.xlsx", config.ReportOutput.FileOutputPath, config.ReportFileName, datetimeStartUtc.ToString("yyyyMMddHHmmss"), datetimeEndUtc.ToString("yyyyMMddHHmmss"));

            // Pre-process the datasets
            Dictionary<String, List<Metric>> dataMetrics = new Dictionary<string, List<Metric>>();
            Dictionary<String, List<Event>> dataEvents = new Dictionary<string, List<Event>>();

            // loop through all the data sets and determine what data would be required
            foreach (ReportDataset dataset in config.ReportDatasets)
            {
                // loop through the pages to see what datasets would be required
                foreach (ReportPage page in config.ReportPages)
                {
                    if (page.DatasetName == dataset.DatasetName)
                    {
                        if (page.PageType == ReportPageType.Metric)
                        {
                            if (!dataMetrics.ContainsKey(dataset.DatasetName))
                            {
                                dataMetrics.Add(dataset.DatasetName, (new PerformanceCounterDataSourceProcessor(dataset.StorageAccountName, dataset.StorageAccountKey, dataset.DeploymentId, dataset.RoleName)).GetMetrics(datetimeStartUtc, datetimeEndUtc));
                            }
                        }
                        else if (
                            page.PageType == ReportPageType.EventDetail
                            | page.PageType == ReportPageType.EventSummary
                            )
                        {
                            if (!dataEvents.ContainsKey(dataset.DatasetName))
                            {
                                //TODO: Better parsing and cast, contain errors
                                // Read and process parameters

                                dataEvents.Add(dataset.DatasetName, 
                                    (new TraceDataSourceProcessor(dataset.StorageAccountName, dataset.StorageAccountKey, dataset.DeploymentId, dataset.RoleName, 
                                        (String)ConfigurationFileProcessor.GetParameterFromDictionaryOrDefaultValue(dataset.Parameters, CONFIG_PARAMETER_EVENT_REGEXMATCH,""))
                                        ).GetEvents(datetimeStartUtc, datetimeEndUtc,
                                        int.Parse(ConfigurationFileProcessor.GetParameterFromDictionaryOrDefaultValue(dataset.Parameters, CONFIG_PARAMETER_EVENT_ID,-1).ToString()),
                                        int.Parse(ConfigurationFileProcessor.GetParameterFromDictionaryOrDefaultValue(dataset.Parameters, CONFIG_PARAMETER_EVENT_MAXROWCOUNT,-1).ToString())
                                        )
                                        );
                            }
                        }
                    }
                }
            }

            // Build the excel report
            ExcelReportBuilder excel = new ExcelReportBuilder();

            foreach (ReportPage page in config.ReportPages)
            {
                //TODO: Replace the dataset name with the storage account name?
                ReportPageSummary reportPageSummary = new ReportPageSummary() { ObjectName = page.PageTitle, StartDateTimeUtc = DateTime.UtcNow.AddMinutes(config.DateTimeStartMinutesOffset), EndDateTimeUtc = DateTime.UtcNow.AddMinutes(config.DateTimeStartMinutesOffset) };

                switch (page.PageType)
                {
                    case ReportPageType.EventDetail:
                        excel.AddEventPage(page.PageTitle, page.SheetName, reportPageSummary, dataEvents[page.DatasetName],(String)ConfigurationFileProcessor.GetParameterFromDictionaryOrDefaultValue(page.Parameters,CONFIG_PARAMETER_EVENT_REGEXMATCH,String.Empty));
                        break;
                    case ReportPageType.EventSummary:
                        //TODO: Process the page parameters to the the summary types
                        //excel.AddEventSummaryPage(page.PageName,reportPageSummary,dataEvents[page.DatasetName],)
                        break;
                    case ReportPageType.Metric:
                        excel.AddMetricPage(page.PageTitle, page.SheetName, reportPageSummary, dataMetrics[page.DatasetName]);
                        break;
                    default:
                        //TODO: throw an exception
                        break;
                }

            }

            // Export the report
            excel.ExportReport(fileAndPath);

            return fileAndPath;
        }


        #endregion

        #region Event
        public void AddEventPage(String pageName, String sheetName, ReportPageSummary summary,  List<Event> events, String messageFilterRegexMatch = null)
        {
            // Filter the messages if needed
            if(messageFilterRegexMatch != null && messageFilterRegexMatch != String.Empty)
            {
                List<Event> filteredEvents = new List<Event>();
                
                foreach(Event e in events)
                {
                    try
                    {
                        if (Regex.IsMatch(e.Message, messageFilterRegexMatch))
                            filteredEvents.Add(e);
                    } catch (Exception ex)
                    {
                        //TODO: Log
                    }
                }

                events = filteredEvents;
            }

            String dataSheetName = sheetName;
            _pkg.Workbook.Worksheets.Add(dataSheetName);
            ExcelWorksheet datasheet = _pkg.Workbook.Worksheets[dataSheetName];

            //Worksheet Header
            WriteWorksheetPageHeader(datasheet, summary);

            // Format
            SetWorksheetColumnWidth(datasheet, 1, 12, 20, 12, 12, 12, 18, 8, 100);
            datasheet.Column(8).Style.WrapText = true;

            // Header
            WriteWorksheetRow(datasheet, 2, 1, "Deployment", "ObjectName","Channel","Source","Level","DateTime","Id","Message");

            int r = 3;
            foreach(Event ev in events)
            {
                WriteWorksheetRow(datasheet, r, 1, ev.DeploymentId, ev.ObjectIdentifier, ev.Channel, ev.Source, ev.Level, ev.EventDateTime.ToString("yyyy-MM-dd hh:mm:ss"), ev.EventId,ev.Message);
                datasheet.Row(r).Height = 14;
                r++;
            }

            FormatSelectionAsTable(datasheet, "Events", 2, 1, r - 1, 8);
        }
        #endregion

        #region Event Summary
        public void AddEventSummaryPage(String pageName, ReportPageSummary summary, List<Event> events, EventSummaryType[] summaryTypes, String filter = "")
        {
            if(filter == String.Empty)
            {
                filter = @".*";
            }

            String dataSheetName = pageName;
            _pkg.Workbook.Worksheets.Add(dataSheetName);
            ExcelWorksheet datasheet = _pkg.Workbook.Worksheets[dataSheetName];

            WriteWorksheetPageHeader(datasheet, summary);

            SetWorksheetColumnWidth(datasheet, 1, 36, 20, 15, 15, 9, 8, 10, 100);

            int startingRow = 2;

            foreach (EventSummaryType type in summaryTypes)
            {

                switch (type)
                {
                    case EventSummaryType.ByDeploymentByRoleByLevel:
                        startingRow = AddEventSubPage_ByDeploymentByRoleByLevel(events, datasheet, startingRow);
                        break;
                    case EventSummaryType.ByDeploymentByRoleByMessage:
                        startingRow = AddEventSubPage_ByDeploymentByRoleByMessage(events, datasheet, startingRow, filter);
                        break;
                    case EventSummaryType.ByDeploymentByRoleByChannelBySourceByLevel:
                        startingRow = AddEventSubPage_ByDeploymentByRoleByChannelBySourceByLevel(events, datasheet, startingRow);
                        break;
                    case EventSummaryType.ByDeploymentByRoleByChannelBySourceByLevelByEventId:
                        startingRow = AddEventSubPage_ByDeploymentByRoleByChannelBySourceByLevelByEventId(events, datasheet, startingRow);
                        break;
                }
            }
        }

        private int AddEventSubPage_ByDeploymentByRoleByMessage(List<Event> events, ExcelWorksheet worksheet, int startingRow, String filter)
        {
            int workingRow = startingRow;
            String subPageName = "By Deployment By Role By Message";

            // Group By Role by Id
            var eventsRoleById = from e in events
                                 group e by new { e.DeploymentId, e.RoleIdentifier, FilteredMessage = Regex.Match(e.Message,filter).Value } into ev
                                 select new { ev.Key.DeploymentId, ev.Key.RoleIdentifier, ev.Key.FilteredMessage, Count = ev.Count() };

            WriteWorksheetHeaderRow(worksheet, subPageName, workingRow, 1);

            workingRow++;

            // Table Headers
            WriteWorksheetRow(worksheet, workingRow, 1, "Deployment", "Role", " ", "  ", "   ", "    ", "Count","Message");
            workingRow++;


            foreach (var e in eventsRoleById.OrderByDescending(e => e.Count))
            {
                WriteWorksheetRow(worksheet, workingRow, 1, e.DeploymentId, e.RoleIdentifier, "", "", "", "", e.Count, e.FilteredMessage);
                workingRow++;
            }

            FormatSelectionAsTable(worksheet, subPageName, startingRow+1, 1, workingRow-1, 8);

            return ++workingRow;
        }

        private int AddEventSubPage_ByDeploymentByRoleByLevel(List<Event> events, ExcelWorksheet worksheet, int startingRow)
        {
            int workingRow = startingRow;
            String subPageName = "By Deployment By Role By Level";

            // Group By Role by Id
            var eventsRoleById = from e in events
                                 group e by new { e.DeploymentId, e.RoleIdentifier, e.Level } into ev
                                 select new { ev.Key.DeploymentId, ev.Key.RoleIdentifier, ev.Key.Level, Count = ev.Count() };

            WriteWorksheetHeaderRow(worksheet, subPageName, workingRow, 1);

            workingRow++;

            // Table Headers
            WriteWorksheetRow(worksheet, workingRow, 1, "Deployment", "Role", " ", "  ", "Level", "   ", "Count");
            workingRow++;


            foreach (var e in eventsRoleById.OrderByDescending(e => e.Count))
            {
                WriteWorksheetRow(worksheet, workingRow, 1, e.DeploymentId, e.RoleIdentifier, "", "", e.Level, "", e.Count);
                workingRow++;
            }

            FormatSelectionAsTable(worksheet, subPageName, startingRow + 1, 1, workingRow - 1, 7);

            return ++workingRow;
        }

        private int AddEventSubPage_ByDeploymentByRoleByChannelBySourceByLevel(List<Event> events, ExcelWorksheet worksheet, int startingRow)
        {
            int workingRow = startingRow;
            String subPageName = "By Deployment By Role By Channel By Source By Level";

            // Group By Role by Id
            var eventsRoleById = from e in events
                                 group e by new { e.DeploymentId, e.RoleIdentifier, e.Channel, e.Source, e.Level } into ev
                                 select new { ev.Key.DeploymentId, ev.Key.RoleIdentifier, ev.Key.Channel, ev.Key.Source, ev.Key.Level, Count = ev.Count() };


            WriteWorksheetHeaderRow(worksheet, subPageName, workingRow, 1);
            workingRow++;

            // Table Headers
            WriteWorksheetRow(worksheet, workingRow, 1, "Deployment", "Role", "Channel", "Source", "Level", " ", "Count");
            workingRow++;

            foreach (var e in eventsRoleById.OrderByDescending(e => e.Count))
            {
                WriteWorksheetRow(worksheet, workingRow, 1, e.DeploymentId, e.RoleIdentifier, e.Channel, e.Source, e.Level, "", e.Count);
                workingRow++;
            }

            FormatSelectionAsTable(worksheet, subPageName, startingRow + 1, 1, workingRow - 1, 7);

            return ++workingRow;
        }

        private int AddEventSubPage_ByDeploymentByRoleByChannelBySourceByLevelByEventId(List<Event> events, ExcelWorksheet worksheet, int startingRow)
        {
            int workingRow = startingRow;
            String subPageName = "By Deployment By Role By Channel By Source By Level By EventId";

            // Group By Role by Id
            var eventsRoleById = from e in events
                                 group e by new { e.DeploymentId, e.RoleIdentifier, e.Channel, e.Source, e.Level, e.EventId } into ev
                                 select new { ev.Key.DeploymentId, ev.Key.RoleIdentifier, ev.Key.Channel, ev.Key.Source, ev.Key.Level, ev.Key.EventId, Count = ev.Count() };

            WriteWorksheetHeaderRow(worksheet, subPageName, workingRow, 1);
            workingRow++;

            // Table Headers
            WriteWorksheetRow(worksheet, workingRow, 1, "Deployment", "Role", "Channel", "Source", "Level", "EventId", "Count");
            workingRow++;

            foreach (var e in eventsRoleById.OrderByDescending(e => e.Count))
            {
                WriteWorksheetRow(worksheet, workingRow, 1, e.DeploymentId, e.RoleIdentifier, e.Channel, e.Source, e.Level, e.EventId, e.Count);
                workingRow++;
            }

            FormatSelectionAsTable(worksheet, subPageName, startingRow + 1, 1, workingRow - 1, 7);

            return ++workingRow;
        }

        #endregion

        #region Metric

        public void AddMetricPage(String pageName, String sheetName, ReportPageSummary summary, List<Metric> metrics)
        {
            String dataSheetName = String.Format("{0}-D", sheetName);
            String chartSheetName = sheetName;

            _pkg.Workbook.Worksheets.Add(dataSheetName);
            _pkg.Workbook.Worksheets.Add(chartSheetName);

            ExcelWorksheet chartsheet = _pkg.Workbook.Worksheets[chartSheetName];
            ExcelWorksheet datasheet = _pkg.Workbook.Worksheets[dataSheetName];

            datasheet.Hidden = eWorkSheetHidden.VeryHidden;

            //Headers
            datasheet.Cells[1, 1].Value = "ObjectName";
            datasheet.Cells[1, 2].Value = "CounterName";
            datasheet.Cells[1, 3].Value = "DateTime";
            datasheet.Cells[1, 4].Value = "Value";

            //Columns
            //datasheet.Column(3).Style.Numberformat.Format = "yyyy-mm-dd h:mm:ss";
            WriteWorksheetPageHeader(chartsheet, summary);

            int i = 2;
            int start = 2;
            int chartNumber = 1;

            int chartHeight = 500;
            int chartWidth = 1200;

            var distinctCounters = (from m in metrics select m.FullyQualifiedMetricName).Distinct().OrderBy(r => r);

            foreach (var counter in distinctCounters)
            {
                var dataForMetric = from m in metrics
                                    where m.FullyQualifiedMetricName == counter
                                    select m;

                ExcelChart chart = chartsheet.Drawings.AddChart(counter + Guid.NewGuid().ToString(), eChartType.Line);

                //Format
                //chart.Style = eChartStyle.Style3;

                //Title
                chart.Title.Text = counter;
                chart.Title.Font.Size = 11;

                // Position
                chart.SetPosition(((chartNumber * chartHeight) - chartHeight)+30, 0);
                chart.SetSize(chartWidth, chartHeight);

                var objectServices = (from m in dataForMetric
                                      select m.ObjectIdentifier).Distinct();

                //chart.XAxis.Title.Text = "Datetime";
                //chart.XAxis.Format = "yyyy-mm-dd h:mm:ss";

                foreach (var ser in objectServices)
                {
                    start = i;
                    foreach (Metric m in dataForMetric.Where(ms => ms.ObjectIdentifier == ser))
                    {
                        datasheet.Cells[i, 1].Value = m.ObjectIdentifier;
                        datasheet.Cells[i, 2].Value = m.FullyQualifiedMetricName;
                        datasheet.Cells[i, 3].Value = m.SampledDateTime.ToString("yyyy-MM-dd hh:mm:ss");
                        datasheet.Cells[i, 4].Value = m.Value;

                        i++;
                    }

                    var series = chart.Series.Add(datasheet.Cells[String.Format("D{0}:D{1}", start, i)], datasheet.Cells[String.Format("C{0}:C{1}", start, i)]);
                    series.Header = ser;
                }
                chartNumber++;
            }
        }

        #endregion

        #region Report

        private void WriteWorksheetPageHeader(ExcelWorksheet sheet, ReportPageSummary summary)
        {
            sheet.Cells[1, 1].Value = String.Format("{0} ({1} - {2} UTC)", summary.ObjectName, summary.StartDateTimeUtc, summary.EndDateTimeUtc);
            sheet.Cells[1, 1].Style.Font.Bold = true;
            sheet.Cells[1, 1].Style.Font.Size = 14;
            sheet.Row(1).Style.Border.Bottom.Style = ExcelBorderStyle.Thin;

        }

        private void FormatSelectionAsTable(ExcelWorksheet datasheet, String tableName, int startRow, int startColumn, int endRow, int endColumn)
        {
            datasheet.Tables.Add(new ExcelAddressBase(startRow,startColumn,endRow,endColumn), tableName);
            datasheet.Tables[tableName].TableStyle = OfficeOpenXml.Table.TableStyles.Light1;
        }

        private void WriteWorksheetHeaderRow(ExcelWorksheet sheet, String title, int row, int column = 1)
        {
            sheet.Cells[row, column].Value = title;
            sheet.Cells[row, column].Style.Font.Bold = true;
        }

        private void WriteWorksheetRow(ExcelWorksheet sheet, int row, int startingColumn, params Object[] values)
        {
            int i = 0;
            foreach (Object v in values)
            {
                sheet.Cells[row, i + startingColumn].Value = v;
                i++;
            }
        }

        private void SetWorksheetColumnWidth(ExcelWorksheet sheet, int startingColumn, params double[] columnWidthValues)
        {
            for(int i = 0; i<columnWidthValues.Count();i++)
            {
                sheet.Column(startingColumn + i).Width = columnWidthValues[i];
            }
        }

        public void ExportReport(String fileNameAndPath)
        {
            _pkg.SaveAs(new FileInfo(fileNameAndPath));
        }
        #endregion
    }
}
